#include "swpdecisiongraphicitem.h"

#include "../../documents/data/graphicdata.h"
#include "../../undo/basicitemcommands.h"
#include "../../utils/undomanager.h"
#include "graphicitemcommon.h"

#include <lineeditdelegate.h>
#include <validatormanager.h>

#include <QtGui/QGraphicsScene>

using namespace core;
using namespace gui;
using namespace swp;

SwpDecisionGraphicItem::Label::Label(const QString& text,
    SwpDecisionGraphicItem* parent) : RectGraphicItem(
        0, true, false, parent),
        m_textItem(0)
{
    m_textItem = new QGraphicsSimpleTextItem(text, this);

    m_textItem->setPos(PREFERRED_CONTENT_PADDING_SMALL,
        PREFERRED_CONTENT_PADDING_SMALL);

    /*setFlags(flags() | QGraphicsItem::ItemIsMovable |
        QGraphicsItem::ItemIsSelectable);*/
    setBrush(Qt::white);
    setPen(QPen(Qt::gray));

    relayout();
}

QString SwpDecisionGraphicItem::Label::text() const
{
    return m_textItem->text();
}

void SwpDecisionGraphicItem::Label::setText(const QString& text)
{
    m_textItem->setText(text);

    relayout();
}

void SwpDecisionGraphicItem::Label::relayout()
{
    const QRectF childrenRect = childrenBoundingRect();

    setRect(QRectF(0, 0,
        childrenRect.width() + PREFERRED_CONTENT_PADDING_SMALL * 2,
        childrenRect.height() + PREFERRED_CONTENT_PADDING_SMALL * 2));
}

SwpDecisionGraphicItem::SwpDecisionGraphicItem(QGraphicsItem* parent) :
    AbstractPolygonGraphicItem(parent),
    m_labelProp(),
    m_labelPositionProp(),
    m_yesProp(),
    m_noProp(),
    m_connectPointToDirectionMapping(),
    m_yesPropConnector(0),
    m_noPropConnector(0),
    m_yesBranchItem(0),
    m_noBranchItem(0),
    m_label(0),
    m_labelDelegate(0)
{
    QVariantList propsVal;
    propsVal << QObject::tr("none");

    m_labelDelegate = new LineEditDelegate();
    m_labelDelegate->setValidator(
        ValidatorManager::getInstance()->getValidator(
            "DecisionLabelValidator"));

    m_labelProp.Name = "label";
    m_labelProp.Text = QObject::tr("Label");
    m_labelProp.setValue("");
    m_labelProp.setDelegate(m_labelDelegate);

    m_labelPositionProp.Name = "labelPosition";
    m_labelPositionProp.Text = QObject::tr("Label position");
    m_labelPositionProp.setValue(QVariantList() << "left");

    m_yesProp.Name = "yesBranch";
    m_yesProp.Text = QObject::tr("Yes branch");
    m_yesProp.setValue(propsVal);

    m_noProp.Name = "noBranch";
    m_noProp.Text = QObject::tr("No branch");
    m_noProp.setValue(propsVal);

    QPolygonF polygon;
    polygon << QPointF(25, 0) << QPointF(50, 25) << QPointF(25, 50)
        << QPointF(0, 25);

    setPolygonF(polygon);

    setFlags(flags() | QGraphicsItem::ItemIsMovable |
        QGraphicsItem::ItemIsSelectable);

    m_connectPointToDirectionMapping["top"] = QPointF(25, 0);
    m_connectPointToDirectionMapping["right"] = QPointF(50, 25);
    m_connectPointToDirectionMapping["bottom"] = QPointF(25, 50);
    m_connectPointToDirectionMapping["left"] = QPointF(0, 25);

    setSingleConnectPointMode(true);
}

SwpDecisionGraphicItem::~SwpDecisionGraphicItem()
{
    delete m_labelDelegate;
}

void SwpDecisionGraphicItem::handleConnectorPositionChange()
{
    AbstractPolygonGraphicItem::handleConnectorPositionChange();

    const QList<ConnectionData> _connections = connections();

    foreach (const ConnectionData& _connection, _connections)
    {
        if (_connection.Connector == m_yesPropConnector)
        {
            const QPointF conPoint = m_connectPoints[
                m_connectionToConnectorMapping[_connection]];

            const QString& value =
                m_connectPointToDirectionMapping.key(conPoint, "");

            QVariantList propsVal;
            propsVal << value;

            m_yesProp.setValue(propsVal);
        }
        else if (_connection.Connector == m_noPropConnector)
        {
            const QPointF conPoint = m_connectPoints[
                m_connectionToConnectorMapping[_connection]];

            const QString& value =
                m_connectPointToDirectionMapping.key(conPoint, "");

            QVariantList propsVal;
            propsVal << value;

            m_noProp.setValue(propsVal);
        }
    }

    QString val = m_yesProp.value().toList().first().toString();

    if (val != "none" && m_yesPropConnector == 0)
    {
        changeConnectionLabel(m_connectPointToDirectionMapping[val],
            QObject::tr("[yes]"),
            m_yesPropConnector, m_yesBranchItem);
    }

    val = m_noProp.value().toList().first().toString();

    if (val != "none" && m_noPropConnector == 0)
    {
        changeConnectionLabel(m_connectPointToDirectionMapping[val],
            QObject::tr("[no]"),
            m_noPropConnector, m_noBranchItem);
    }
}

IEditorItem* SwpDecisionGraphicItem::yesBranchItem() const
{
    return m_yesBranchItem;
}

IEditorItem* SwpDecisionGraphicItem::noBranchItem() const
{
    return m_noBranchItem;
}

void SwpDecisionGraphicItem::connectionsRefreshed()
{
    AbstractPolygonGraphicItem::connectionsRefreshed();

    QString val = m_yesProp.value().toList().first().toString();

    if (val != "none" && m_yesPropConnector == 0)
    {
        changeConnectionLabel(m_connectPointToDirectionMapping[val],
            QObject::tr("[yes]"),
            m_yesPropConnector, m_yesBranchItem);
    }

    val = m_noProp.value().toList().first().toString();

    if (val != "none" && m_noPropConnector == 0)
    {
        changeConnectionLabel(m_connectPointToDirectionMapping[val],
            QObject::tr("[no]"),
            m_noPropConnector, m_noBranchItem);
    }
}

bool SwpDecisionGraphicItem::supportsConnection(
    const ConnectionData& connection) const
{
    bool ret = false;

    const int type = connection.From != 0 ? connection.From->type() :
        INVALID_TYPE;

    const QList<ConnectionData> _connections = connections();

    const bool predecision = (connection.To == this ?
        incomingConnectionSize(_connections) < 2 : true) &&
        (connection.From == 0 ?
             outgoingConnectionSize(_connections) < 2 : true);

    ret = predecision && (connection.From == 0 ||
        ((type == SWP_ACTIVITY_ITEM || type == SWP_JOIN_ITEM ||
        type == SWP_SPLIT_ITEM || type == SWP_MERGE_ITEM ||
        type == SWP_DECISION_ITEM) &&
            connection.ConnectionId == CONNECTION_ACTIVITY &&
            !containsSameConnection(connection, _connections)));

    return ret;
}

int SwpDecisionGraphicItem::type() const
{
    return SWP_DECISION_ITEM;
}

QString SwpDecisionGraphicItem::name() const
{
    return QObject::tr("Decision");
}

IEditorItem* SwpDecisionGraphicItem::copy() const
{
    return new SwpDecisionGraphicItem(0);
}

QList<Property> SwpDecisionGraphicItem::properties() const
{
    QList<Property> ret;

    ret.append(m_labelProp);
    ret.append(m_labelPositionProp);
    ret.append(m_yesProp);
    ret.append(m_noProp);

    return ret;
}

QVariant SwpDecisionGraphicItem::propertyValue(
    const QString& propertyName) const
{
    QVariant ret;

    if (propertyName == m_yesProp.Name)
    {
        ret = m_yesProp.value();
    }
    else if (propertyName == m_noProp.Name)
    {
        ret = m_noProp.value();
    }
    else if (propertyName == m_labelProp.Name)
    {
        ret = m_labelProp.value();
    }
    else if (propertyName == m_labelPositionProp.Name)
    {
        ret = m_labelPositionProp.value();
    }

    return ret;
}

void SwpDecisionGraphicItem::propertyChanged(const QString& property,
    const QVariant& newValue, bool silent)
{  
    QVariantList propsVal;
    propsVal << newValue;

    bool changed = false;

    if (property == m_yesProp.Name &&
        m_yesProp.value().toList().first() != newValue)
    {
        if (!silent)
        {
            PropertyChangeCommand* command = new PropertyChangeCommand(
                this, m_yesProp, newValue);

            UndoManager::getInstance()->push(
                dynamic_cast<IDocumentData*>(scene()), command);
        }

        m_yesProp.setValue(propsVal);

        if (silent && isSelected()) {
            setSelected(false);
            setSelected(true);
        }

        if (silent) {
            if (newValue.toString() != "none")
            {
                changeConnectionLabel(
                    m_connectPointToDirectionMapping[newValue.toString()],
                    QObject::tr("[yes]"), m_yesPropConnector, m_yesBranchItem);
            }
            else
            {
                resetConnectionLabel(m_yesPropConnector, m_yesBranchItem);
            }
        }

        changed = true;
    }
    else if (property == m_noProp.Name &&
        m_noProp.value().toList().first() != newValue)
    {
        if (!silent)
        {
            PropertyChangeCommand* command = new PropertyChangeCommand(
                this, m_noProp, newValue);

            UndoManager::getInstance()->push(
                dynamic_cast<IDocumentData*>(scene()), command);
        }

        m_noProp.setValue(propsVal);

        if (silent && isSelected()) {
            setSelected(false);
            setSelected(true);
        }

        if (silent) {
            if (newValue.toString() != "none")
            {
                changeConnectionLabel(
                    m_connectPointToDirectionMapping[newValue.toString()],
                    QObject::tr("[no]"), m_noPropConnector, m_noBranchItem);
            }
            else
            {
                resetConnectionLabel(m_noPropConnector, m_noBranchItem);
            }
        }

        changed = true;
    }
    else if (property == m_labelPositionProp.Name &&
        m_labelPositionProp.value().toList().first() != newValue)
    {
        if (!silent)
        {
            PropertyChangeCommand* command = new PropertyChangeCommand(
                this, m_labelPositionProp, newValue);

            UndoManager::getInstance()->push(
                dynamic_cast<IDocumentData*>(scene()), command);
        }

        m_labelPositionProp.setValue(propsVal);

        if (silent && isSelected()) {
            setSelected(false);
            setSelected(true);
        }

        if (silent && !m_labelProp.value().toString().isEmpty()) {
            changeLabelPosition(
                m_connectPointToDirectionMapping[newValue.toString()]);
        }

        changed = true;
    }
    else if (property == m_labelProp.Name &&
        m_labelProp.value() != newValue)
    {
        if (!silent)
        {
            PropertyChangeCommand* command = new PropertyChangeCommand(
                this, m_labelProp, newValue);

            UndoManager::getInstance()->push(
                dynamic_cast<IDocumentData*>(scene()), command);
        }

        m_labelProp.setValue(newValue);

        if (silent && isSelected()) {
            setSelected(false);
            setSelected(true);
        }

        if (silent) {
            changeLabel(newValue.toString());
        }

        changed = true;
    }

    if (silent && changed)
    {
        GraphicData* data = dynamic_cast<GraphicData*>(m_documentData);

        Q_ASSERT(data != 0); // shouldn't be null

        data->emitDataChanged();
    }
}

QVariantList SwpDecisionGraphicItem::optionList(
    const QString& propertyName) const
{
    QVariantList ret;

    if (propertyName == m_yesProp.Name || propertyName == m_noProp.Name)
    {
        ret << QObject::tr("none") << QObject::tr("top") <<
            QObject::tr("left") << QObject::tr("bottom") <<
            QObject::tr("right");
    }
    else if (propertyName == m_labelPositionProp.Name)
    {
        ret << QObject::tr("top") <<
            QObject::tr("left") << QObject::tr("bottom") <<
            QObject::tr("right");
    }

    return ret;
}

QVariantList SwpDecisionGraphicItem::autocomplete(
    const QString& propertyName) const
{
    QVariantList ret;

    return ret;
}

QList<QPointF> SwpDecisionGraphicItem::scalePoints() const
{
    QList<QPointF> ret;

    ret << QPointF(25, 0) << QPointF(50, 25) << QPointF(25, 50)
        << QPointF(0, 25);

    return ret;
}

QList<QPointF> SwpDecisionGraphicItem::connectPoints() const
{
    QList<QPointF> ret;

    ret << QPointF(25, 0) << QPointF(50, 25) << QPointF(25, 50)
        << QPointF(0, 25);

    return ret;
}

QList<QPointF> SwpDecisionGraphicItem::selfConnectAnchorPoints() const
{
    QList<QPointF> ret;

    ret << QPointF(25.0, -20) << QPointF(70.0, 25.0)
        << QPointF(25.0, 70.0) << QPointF(-20, 25.0);

    return ret;
}

bool SwpDecisionGraphicItem::containsSameConnection(
    const ConnectionData& connection,
    const QList<ConnectionData>& connections) const
{
    bool ret = false;

    foreach (const ConnectionData& _connection, connections)
    {
        if (ConnectionComparator::compare(connection, _connection,
            ConnectionComparator::WithoutConnector))
        {
            ret = true;
            break;
        }
    }

    return ret;
}

int SwpDecisionGraphicItem::incomingConnectionSize(
    const QList<ConnectionData>& connections) const
{
    int ret = 0;

    foreach (const ConnectionData& _connection, connections)
    {
        if (_connection.To == this)
        {
            ++ret;
        }
    }

    return ret;
}

int SwpDecisionGraphicItem::outgoingConnectionSize(
    const QList<ConnectionData>& connections) const
{
    int ret = 0;

    foreach (const ConnectionData& _connection, connections)
    {
        if (_connection.From == this)
        {
            ++ret;
        }
    }

    return ret;
}

void SwpDecisionGraphicItem::changeConnectionLabel(
    const QPointF& connectPoint,
    const QString& label,
    IEditorItem*& connectorItem,
    IEditorItem*& connecteeItem)
{
    QPointF _connectPoint = scenePos();
    _connectPoint.rx() += connectPoint.x();
    _connectPoint.ry() += connectPoint.y();

    const QList<ConnectionData> _connections = connections();

    foreach (const ConnectionData& _connection, _connections)
    {
        if (_connection.From == this)
        {
            AbstractLineGraphicItem* connector =
                dynamic_cast<AbstractLineGraphicItem*>(_connection.Connector);

            Q_ASSERT(connector != 0); // all connectors should be AbsLineItem

            if (nearestConnectionPoint(
                connector->startPoint(), Outgoing, true) == _connectPoint)
            {
                connectorItem = _connection.Connector;
                connecteeItem = _connection.To;

                connector->setLabel(AbstractLineGraphicItem::MiddleAbove,
                    label);

                break;
            }
        }
    }
}

void SwpDecisionGraphicItem::resetConnectionLabel(IEditorItem*& connectorItem,
    IEditorItem*& connecteeItem)
{
    if (connectorItem != 0)
    {
        AbstractLineGraphicItem* connector =
            dynamic_cast<AbstractLineGraphicItem*>(connectorItem);

        Q_ASSERT(connector != 0); // all connectors should be AbsLineItem

        connector->setLabel(AbstractLineGraphicItem::MiddleAbove,
            "");

        connectorItem = 0;
        connecteeItem = 0;
    }
}

void SwpDecisionGraphicItem::changeLabel(const QString& label)
{
    if (m_label == 0)
    {
        m_label = new Label(label, this);
        changeLabelPosition(m_connectPointToDirectionMapping[
            m_labelPositionProp.value().toList().first().toString()]);
    }
    else if (label.isEmpty())
    {
        delete m_label;
        m_label = 0;
    }
    else
    {
        m_label->setText(label);
        changeLabelPosition(m_connectPointToDirectionMapping[
            m_labelPositionProp.value().toList().first().toString()]);
    }
}

void SwpDecisionGraphicItem::changeLabelPosition(const QPointF& connectPoint)
{
    if (m_label != 0)
    {
        const QRectF _rect = m_label->boundingRect();
        if (connectPoint.x() == 0)
        {
            m_label->setPos(-(_rect.width() + PREFERRED_CONTENT_ITEM_SPACING),
                connectPoint.y() - (_rect.height()/2));
        }
        else if (connectPoint.x() == 50)
        {
            m_label->setPos(connectPoint.x() + PREFERRED_CONTENT_ITEM_SPACING,
                connectPoint.y() - (_rect.height()/2));
        }
        else if (connectPoint.y() == 0)
        {
            m_label->setPos(connectPoint.x() - (_rect.width()/2),
                -(_rect.height() + PREFERRED_CONTENT_ITEM_SPACING));
        }
        else
        {
            m_label->setPos(connectPoint.x() - (_rect.width()/2),
                connectPoint.y() + PREFERRED_CONTENT_ITEM_SPACING);
        }
    }
}
